package dryven.model.binding.form;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import dryven.annotations.DII;
import dryven.config.ApplicationConfiguration;
import dryven.model.binding.fieldset.Field;
import dryven.model.binding.fieldset.FieldGroup;
import dryven.model.binding.fieldset.FieldSet;
import dryven.model.binding.proxy.Binding;
import dryven.model.validation.ErrorMessage;
import dryven.request.controller.ControllerDescription;
import dryven.request.controller.result.ActionResult;
import dryven.request.controller.result.OriginatingControllerListener;
import dryven.request.http.Request;
import dryven.request.routing.RoutingService;
import dryven.util.crypt.AESGeneratedKey;
import dryven.util.json.dryvenjson.JSON;

public class Form implements OriginatingControllerListener {
	private FieldSet _fieldSet;
	private Iterable<FormGroup> _formGroups;
	private Binding<FieldSet> _fieldSetBinding;
	private Map<Class<?>,Object> _boundModelsByType;
	private ControllerDescription _formAction;
	private String _signSecret;
	private AESGeneratedKey _cryptKey;
	private RoutingService _routingService;
	private ControllerDescription _errorAction;
	private FormValidation _validation;
	
	public static final String SignatureField = "__dryven_sig";

	public Form(Binding<FieldSet> binding, Binding<ActionResult> formAction, FormValidation validation) {
		_fieldSetBinding = binding;
		_fieldSet = binding.invoke();
		if(formAction!=null) {
			_formAction = new ControllerDescription(formAction);
		}
		if(validation!=null && validation.belongsToForm(this)) {
			_validation = validation;
		}
	}

	public void injectDependencies(
			@DII(type = ApplicationConfiguration.class, method = "getSignSecret") String signSecret,
			AESGeneratedKey cryptKey, 
			RoutingService routing) {
		_signSecret = signSecret;
		_cryptKey = cryptKey;
		_routingService = routing;
	}

	public void bindModel(Object model) {
		if(hasErrors()) {
			return;
		}
		if(_boundModelsByType==null) {
			_boundModelsByType = new HashMap<Class<?>,Object>();
		}
		if(!_boundModelsByType.containsKey(model.getClass())) {
			_boundModelsByType.put(model.getClass(),model);
		}
	}
	
	@JSON
	public Iterable<FormGroup> getGroups() {
		if(_formGroups==null) {
			Collection<FormGroup> formGroups = new ArrayList<FormGroup>();
			for (FieldGroup group : _fieldSet.groups()) {
				formGroups.add(new FormGroup(group,this));
			}
			_formGroups = formGroups;
		}
		return _formGroups;
	}

	@JSON
	public String getActionURL() {
		return _routingService.controllerToRoute(_formAction);
	}

	public ControllerDescription getAction() {
		return _formAction;
	}

	@JSON
	public String getSignature() {
		String errorURL = _routingService.controllerToRoute(_errorAction);
		return new FormSignatureEncoder(_cryptKey, _fieldSetBinding,
				_fieldSet, _signSecret, _boundModelsByType, errorURL).encode();
	}
	
	public Object getFieldValue(Field field) {
		Object model = _boundModelsByType.get(field.getModelType());
		if(model==null && _validation!=null) {
			model = _validation.getValidatedModel(field.getModelType());
		}
		if(model==null) {
			return null;
		}
		return field.getValue(model);
	}
	
	@JSON
	public boolean hasErrors() {
		return _validation!=null && _validation.hasErrors();
	}
	
	public void setErrorAction(ControllerDescription cd) {
		_errorAction = cd;
	}

	@Override
	public void passOriginatingController(ControllerDescription cd) {
		setErrorAction(cd);
	}

	public ErrorMessage getFieldError(Field field) {
		if(_validation==null) {
			return null;
		}
		return _validation.getErrorMessages().getMessageByName(field.getName());
	}
}
